/*
 * Created on Mar 3, 2005
 *
 * TODO To change the template for this generated file go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
package jxta.chat.voice;

/**
 * @author Ravi
 * @modified 2005-22-03 jamoore added capacity accessor and logger
 * TODO To change the template for this generated type comment go to Window -
 * Preferences - Java - Code Style - Code Templates
 */

import java.nio.ByteBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;


/**
 *  FIFO buffer.
 *  @author ravi
 *  @modified 2005-23-03 jamoore add dynamic buffer resize
 *  @modified 2005-23-03 jamoore add capacity accessor, audioresource and res name
 */
public final class VoiceDataBuffer {
    private static final Logger LOG = Logger.getLogger (VoiceDataBuffer.class.getName());
    private ByteBuffer buffer = null;
    private int length;
    private int current;
    
    private final String audioResourceName;
    
    private final AudioResource audioResource;
    
    private boolean bufferOverFlow = false;
    /**
     *
     */
    public VoiceDataBuffer (int size, AudioResource audioResource, String audioResourceName) {
        super ();
        
        LOG.setLevel (Level.INFO);
        this.audioResource = audioResource;
        this.audioResourceName = audioResourceName;
        buffer = ByteBuffer.allocate (size);
        length = 0;
        current = 0;
    }
    
    public int getCapacity () {
        
        return buffer.capacity ();
    }
    
    /**
     *  Returns if there has been a buffer overflow.
     *  Not sync'd
     */
    public boolean isBufferOverFlow () {
        
        boolean rtn = bufferOverFlow;
        
        if(bufferOverFlow == true) {
            
            bufferOverFlow = false;
        }
        
        return rtn;
        
    }
    
    public byte[] get (int len) {
        synchronized (buffer) {
            if (len > length) {
                System.err.println ("error: len larger than buffer content length");
                return null;
            }
            byte[] buf = new byte[len];
            byte[] rem = new byte[length - len];
            //			System.out.println("---popping len: " + len + " remlen: " +
            // rem.length);
            buffer.position (0);
            buffer.get (buf, 0, len);
            //			System.out.println("---got buf: " + arrayToString(buf));
            length -= len; // size of contents in buffer is minus len
            //			System.out.println("---length: " + length + " position: " +
            // buffer.position());
            current = buffer.position ();
            //buffer.get(rem, current, length); // get remainder of buffer
            // contents
            for (int i = 0; i < length; i++) {
                rem[i] = buffer.get ();
            }
            //			System.out.println("---got rem: " + arrayToString(rem));
            buffer.position (0); // reset buffer position to front of buffer
            buffer.put (rem); // shift remainder to beginning of buffer eg.
            // buffer.put(rem, 0, length);
            current = buffer.position ();
            //			System.out.println("---current: " + current);
            //clear(buffer.position()); // may not be necessary
            return buf;
        }
        
    }
    
    /**
     *  Appends the given byte array to the end of this buffer. If a buffer
     *  overflow occurs a new buffer is allocated and it's size is increased
     *  acording to the audio resource's (speaker, mic) current buffer spec.
     *  @modified 2005-23-03 jamoore add dynamic buffer increase
     */
    public void append (byte[] array) {
        
        synchronized (buffer) {
            
            if (array.length + length > buffer.capacity ()) {
                
                synchronized(buffer) {
                    bufferOverFlow = true; // only used for debugging, no sync needed
                    
                    LOG.setLevel (Level.INFO);
                    if(LOG.getLevel () == Level.INFO) {
                        LOG.info("buffer overflow error in " + audioResourceName +
                                 " : current size " + this.size() + " capacity " +
                                 buffer.capacity());
                    }
                    
                    ByteBuffer tmpBuffer = ByteBuffer.allocate (buffer.capacity () +
                            audioResource.getBufferSize ());
                    
                    tmpBuffer.put (buffer);
                    
                    buffer = null;
                    
                    buffer = tmpBuffer;
                    
                    tmpBuffer = null;
                    
                }
                
                if(LOG.getLevel () == Level.INFO) {
                    LOG.info("adjusting buffer size to " + buffer.capacity());
                }
                
            }
            
            buffer.put (array);
            
            length += array.length;
            
            current = buffer.position ();
        }
    }
    
    /**
     *  Appends a single byte onto the buffer. Behaves exactly as if a call to
     *  append(byte[]) with a byte[1] arg
     *  @modified 2005-23-03 jamoore this method now calls append(byte[])
     */
    public void append (byte b) {
        
        byte[] buff = new byte[1];
        
        buff[0] = b;
        
        append (buff);
        
    }
    
    public int size () {
        synchronized (buffer) {
            return length;
        }
    }
    
    /*
     * must be called in synchronized block
     */
    private void clear (int offset) {
        for (int i = offset; i < buffer.capacity (); i++) {
            buffer.put (i, (byte) 0);
        }
        buffer.position (offset); // reset position to offset index value
    }
    
    private void reset () {
        buffer.position (current);
    }
    
    public void printBuffer () {
        synchronized (buffer) {
            System.out.println (audioResourceName+" Length of buffer contents: " + size ()
            + ", current position: " + buffer.position ());
            System.out.print ("buffer: ");
            buffer.position (0);
            for (int i = 0; i < buffer.capacity (); i++) {
                String str = (i == 0) ? "[" : ", ";
                System.out.print (str + (char) buffer.get ());
            }
            System.out.println ("]");
            reset ();
        }
    }
    
    public static String arrayToString (byte[] array) {
        String string = "";
        if (array.length <= 0)
            return string;
        for (int i = 0; i < array.length; i++) {
            string += (i == 0) ? "[" : ", ";
            string += (char) array[i];
        }
        string += "]";
        return string;
    }
    
    public void printBuffer (int offset, int len) {
        synchronized (buffer) {
            System.out.println ("Length of buffer contents: " + size ()
            + ", current position: " + buffer.position ());
            System.out.print ("Voice Data Buffer Content : ");
            buffer.position (offset);
            for (int i = 0; i < len; i++) {
                String str = (i == 0) ? "[" : ",";
                System.out.print (str + (char) buffer.get ());
            }
            System.out.println ("]");
            reset ();
        }
    }
    
    public static void main (String[] args) {
        /*
        System.out.println ("Testing VoiceDatBuffer...");
        //VoiceDataBuffer mybuff = new VoiceDataBuffer (10,"Test");
        mybuff.printBuffer ();
        byte[] a = { 'a', 'b', 'c' };
        mybuff.append (a);
        mybuff.printBuffer ();
        byte[] b = { 'd', 'e', 'f', 'g' };
        mybuff.append (b);
        mybuff.printBuffer ();
        byte[] c = { 'h', 'i', 'j', 'k' };
        mybuff.append (c);
        mybuff.printBuffer ();
         
        byte[] d = mybuff.get (4);
        System.out.println ("d.len: " + d.length + " " + arrayToString (d));
        mybuff.printBuffer ();
         
        mybuff.append (c);
        mybuff.printBuffer ();
         
        mybuff.get (7);
        mybuff.printBuffer ();
         
        mybuff.append ((byte) 'a');
        mybuff.printBuffer ();
         
        mybuff.append (c);
        mybuff.append (c);
        mybuff.append ((byte) 'b');
        mybuff.printBuffer ();
         
        mybuff.append ((byte) 'c');
        mybuff.printBuffer ();
         
        byte[] e = mybuff.get (9);
        mybuff.printBuffer ();
         
        mybuff.get (2);
        mybuff.printBuffer ();
         
        System.exit (0);
         **/
    }
}